/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2008 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

using Anculus.Core;

using Galaxium.Core;
using Galaxium.Protocol;

namespace Galaxium.Protocol.Msn
{
	public class WebcamConnection
	{
		public event EventHandler<WebcamFrameReceivedEventArgs> FrameReceived;
		
		XmlElement _xml;
		MsnSession _session;
		MsnContact _remote;
		byte[] _buffer = new byte[0];
		IConnection _connection;
		List<KeyValuePair <string, int>> _addresses = new List<KeyValuePair <string, int>> ();
		bool _connected;
		
		const string _connectedString = "connected\r\n\r\n";
		
		protected string Auth
		{
			get
			{
				return string.Format ("recipientid={0}&sessionid={1}", RecipientID, SessionID);
			}
		}
		
		public bool Connected
		{
			get { return _connected; }
		}
		
		public int RecipientID
		{
			get { return int.Parse (MsnXmlUtility.FindChild (_xml, "rid").InnerText); }
		}
		
		public int SessionID
		{
			get { return int.Parse (MsnXmlUtility.FindChild (_xml, "session").InnerText); }
		}
		
		public string LocalXml
		{
			get
			{
				//TODO: we need to find an available port
				int port = 80;
				
				//TODO: we need to know our IP addresses, both internal and external
				// We can get our external IP from our profile data
				//--> IP's + ports should be retrieved using the port mapping utility
				
				return "<viewer><version>2.0</version><rid>" + RecipientID +
					"</rid><udprid>" + (RecipientID + 1).ToString () +
					"</udprid><session>" + SessionID.ToString () +
					"</session><ctypes>0</ctypes><cpu>2931</cpu>" +
					"<tcp><tcpport>" + port.ToString () +
					"</tcpport>\t\t\t\t\t\t\t\t  <tcplocalport>" + port.ToString () +
					"</tcplocalport>\t\t\t\t\t\t\t\t  <tcpexternalport>" + port.ToString () +
					"</tcpexternalport><tcpipaddress>192.168.0.1</tcpipaddress></tcp>" +
					"<udp><udplocalport>7786</udplocalport><udpexternalport>31863</udpexternalport><udpexternalip>192.168.0.1" +
					"</udpexternalip><a1_port>31859</a1_port><b1_port>31860</b1_port><b2_port>31861</b2_port>" +
					"<b3_port>31862</b3_port><symmetricallocation>1</symmetricallocation><symmetricallocationincrement>1</symmetricallocationincrement>" +
					"<udpversion>1</udpversion><udpinternalipaddress1>127.0.0.1</udpinternalipaddress1></udp>" +
					"<codec></codec><channelmode>1</channelmode></viewer>\r\n\r\n";
			}
		}
		
		public MsnSession Session
		{
			get { return _session; }
		}
		
		public WebcamConnection (MsnContact remote, XmlElement xml)
		{
			_xml = xml;
			_remote = remote;
			_session = _remote.Session as MsnSession;
			
			int port = int.Parse (MsnXmlUtility.FindElement (_xml, "tcp/tcpport").InnerText);
			
			foreach (XmlNode node in MsnXmlUtility.FindChild (_xml, "tcp"))
			{
				if (node.LocalName.StartsWith ("tcpipaddress"))
					_addresses.Add (new KeyValuePair <string, int> (node.InnerText, port));
			}
			
			_connection = new Galaxium.Protocol.TCPConnection (_session, new WebcamConnectionInfo (_addresses[0].Key, _addresses[0].Value));
			_connection.AfterConnect += OnConnected;
			_connection.Closed += OnClosed;
			_connection.DataReceived += OnDataReceived;
		}
		
		public void Connect ()
		{
			_connection.Connect ();
		}
		
		void OnConnected (object sender, ConnectionEventArgs args)
		{
			Log.Debug ("Connected, sending auth ({0})", Auth);
			
			_connection.Send (Encoding.UTF8.GetBytes (Auth + "\r\n\r\n"));
		}
		
		void OnClosed (object sender, ConnectionEventArgs args)
		{
			Log.Debug ("Closed");
		}
		
		void OnDataReceived (object sender, ConnectionDataEventArgs args)
		{
			AppendToBuffer (args.Buffer, args.Length);
			
			if (!_connected)
			{
				if (_buffer.Length < _connectedString.Length)
					return;
				
				string str = Encoding.UTF8.GetString (ReadBuffer ((uint)_connectedString.Length));
				ShrinkBuffer ((uint)_connectedString.Length);

				if (str != _connectedString)
				{
					Log.Error ("Received something other than connected string");
					Log.Debug (str);
					
					return;
				}
				
				_connected = true;
				return;
			}
			
			if (_buffer.Length < 24)
			{
				//Log.Debug ("Waiting until header is received");
				return;
			}
			
			byte[] header = ReadBuffer (24);
			uint size = (uint)(header[8] + (header[9] << 8) + (header[10] << 16) + (header[11] << 24));
			
			if (_buffer.Length < 24 + size)
			{
				//Log.Debug ("Waiting until {0} bytes received", 24 + size);
				return;
			}
			
			// We now have enough data for a frame
			
			// Remove the header from the buffer
			ShrinkBuffer (24);
			
			byte[] data = ReadBuffer (size);
			ShrinkBuffer (size);
			
			if (FrameReceived != null)
				FrameReceived (this, new WebcamFrameReceivedEventArgs (data, 0, 0));
		}
		
		void AppendToBuffer (byte[] data, int length)
		{
			byte[] newBuffer = new byte[_buffer.Length + length];

			Array.Copy (_buffer, 0, newBuffer, 0, _buffer.Length);
			Array.Copy (data, 0, newBuffer, _buffer.Length, length);

			_buffer = newBuffer;
		}
		
		byte[] ReadBuffer (uint size)
		{
			byte[] data = new byte[size];
			Array.Copy (_buffer, 0, data, 0, size);
			return data;
		}
		
		void ShrinkBuffer (uint skip)
		{
			if (skip > _buffer.Length)
				skip = (uint)_buffer.Length;
			
			byte[] newBuffer = new byte[_buffer.Length - skip];

			if (newBuffer.Length > 0)
				Array.Copy (_buffer, skip, newBuffer, 0, newBuffer.Length);

			_buffer = newBuffer;
		}
		
		class WebcamConnectionInfo : AbstractConnectionInfo
		{
			public WebcamConnectionInfo (string host, int ip)
				: base (host, ip, false)
			{
			}
		}
	}
}
